package echo.netty.hartbeat;

import echo.netty.TestProtocol.MessageCodec;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;

/**
 * @author： zhangnan
 * @date： 2025/5/13 14:48
 * @description： 测试心跳 netty 服务端
 * 使用 IdleStateHandler 和 ChannelDuplexHandler  来处理 读空闲 事件
 * @modifiedBy：
 * @version: 1.0
 */
@Slf4j
public class TestHartbeatServer {

    /**
     * 描述：连接假死
     * 原因
     * 公网络设备出现故障，例如网卡，机房等，底层的 TCP 连接已经断开了，但应用程序没有感知到，仍然占用着资源。
     * 公网网络不稳定，出现丢包。如果连续出现丢包，这时现象就是客户端数据发不出去，服务端也一直收不到数据，就这么一直耗着
     * 应用程序线程阻塞，无法进行数据读写
     * 问题
     * 假死的连接占用的资源不能自动释放
     * 向假死的连接发送数据，得到的反馈是发送超时
     */


    public static void main(String[] args) {
        NioEventLoopGroup boss = new NioEventLoopGroup(1);
        NioEventLoopGroup workers = new NioEventLoopGroup(2);
        try {
            ChannelFuture channelFuture = new ServerBootstrap()
                    .channel(NioServerSocketChannel.class)
                    .group(boss, workers)
                    .childHandler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new LengthFieldBasedFrameDecoder(1024,12,4,0,0));
                            pipeline.addLast(new LoggingHandler(LogLevel.DEBUG));
                            // 编解码器如果有问题，会导致服务端无法解析客户端的心跳包，从而认为读空闲
                            pipeline.addLast(new MessageCodec());
                            // IdleStateHandler 用来判断是不是 读空闲时间过长 或 写空闲时间过长 或 读写空闲时间过长
                            // 示例中 5S 内如果没有收到 channel 的数据，会触发一个 IdleState 的 READER_IDLE 事件
                            // 参数1：读空闲时间；参数2：写空闲时间；参数3：读写空闲时间；单位：秒
                            pipeline.addLast(new IdleStateHandler(5,0,0));
                            // 使用 ChannelDuplexHandler 来处理这个 IdleState 产生的 READER_IDLE 事件
                            // ChannelDuplexHandler可以同时作为入站和出站处理器
                            pipeline.addLast(new ChannelDuplexHandler(){
                                // READER_IDLE 事件属于特殊的自定义事件，向 READER_IDLE 这种特殊事件发生时，会触发 userEventTriggered
                                @Override
                                public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
                                    IdleStateEvent event = (IdleStateEvent) evt;
                                    // 判断触发的是什么事件
                                    if (event.state() == IdleState.READER_IDLE) { // 读空闲事件
                                        log.debug("已经 5S 没有读到数据了！");
                                        ctx.channel().close();
                                    }
//                                    super.userEventTriggered(ctx, evt);
                                }
                            });
                        }
                    })
                    .bind(8080);
            channelFuture.sync();
            channelFuture.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            workers.shutdownGracefully();
            boss.shutdownGracefully();
        }

    }

}
